package org.dei.perla.sys.device.fpc;

import java.util.ArrayList;

import org.dei.perla.component.Component;
import org.dei.perla.parser.clauses.OnUnsupportedSampleRateOptions;
import org.dei.perla.sys.llqruntime.datastructures.Record;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.messages.MsgUtility;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

/**
 * Questa classe astrae un dispositivo fisico connesso al sistema.
 * Si occupa di tradurre e comunicare le richeste verso il 
 * dispositivo e le risposte ricevute.
 * 
 * L'attuale versione è implementata solo a scopo di test.
 */
public abstract class FPC extends Component {
	
	/**
	 * Nome del componente
	 */
	protected String pDeviceName;

	/**
	 * Waiter, riceve i messagi in arrivo sulle pipe
	 */
	protected Waiter<Pipe<AdapterFpcMessage>> pFpcWaiter;
	
	/**
	 * Event listener
	 */
	protected Thread pEventListener;
	/**
	 * Pipe sui quali vengono ricevuti dati dal server
	 */
	protected Pipe<AdapterFpcMessage> pPipeIn;
	/**
	 * Pipe sulla quale vengono scritti i dati per il server
	 */
	protected Pipe<AdapterFpcMessage> pPipeOut;
	
	/**
	 * Messaggio di binding iniziale
	 */
	protected AdapterFpcMessage pFrame;
	
	//protected abstract void setDefault();
	
	/**
	 * Costruttore
	 * 
	 * @param parText
	 * @param parPipeIn, pipe sulla quale si ricevono i dati
	 * @param parPipeOut, pipe sulla quale si mandano i dati
	 */
	public FPC (String parText,
			AdapterFpcMessage parFrame,
			Pipe<AdapterFpcMessage> parPipeIn, 
			Pipe<AdapterFpcMessage> parPipeOut) {
		
		super("Fpc " + parText);
		
		this.pDeviceName = parText;
		this.pFrame = parFrame;
		this.pPipeIn = parPipeIn;
		this.pPipeOut = parPipeOut;
		
		this.pFpcWaiter = new Waiter<Pipe<AdapterFpcMessage>>();
		
		// Attacco la pipe di input al waiter
		this.pFpcWaiter.addWaitable(this.pPipeIn.getWaitable());
	}
	
	public FPC(String text) {
		super(text);
	}
	
	public FPC (){
		super ("FPC");
	}
	
	public void start(){
		
		// Manda ACK al dispositivo
		this.sendAck();
		
		this.pFpcWaiter.start();
		
		//	Avvia il thread di ricezione
		this.pEventListener = new Thread() {
			public void run() {
				pReceive();
			}
		};
		this.pEventListener.start();
	}
	
	/**
	 * Ferma l'esecuzione del componente
	 */
	public void stop(){
		
		this.pFpcWaiter.stop();
		try {
			this.pEventListener.join();
		}
		catch(Exception parException) { }
		this.pEventListener = null;
	}
	
	/**
	 * Si mette in ricezione sulle pipe
	 *
	 */
	public void pReceive(){
		
		Waitable<Pipe<AdapterFpcMessage>> tWaitable = null;
		
		do{
			
			tWaitable = this.pFpcWaiter.waitNext();
			
			if (tWaitable != null){
				
				this.receiveManager();
				
			}
			
		} while(tWaitable != null);
	}
	
	private void receiveManager(){
		
		AdapterFpcMessage tMsg = this.pPipeIn.dequeue();
		byte[] tPayload = tMsg.getPayload();
		
		if(this.pIsNormalMessage(tPayload)){
			
			this.mngNormalMsg(tPayload);
			
		}
	}
	
	abstract protected void mngNormalMsg(byte[] parPayload);
	
	/**
	 * Manda un messaggio di acknowledgment al dispositivo
	 */
	protected void sendAck() {
	
		byte[] tBvciByte = MsgUtility.intToByteArray(MsgUtility.getBvci(this.pFrame.getPayload()));
		
		byte[] tFlag = MsgUtility.intToByteArray(MsgUtility.flagAck);
		
		byte[] tPayload = new byte[tBvciByte.length + tFlag.length];
		
		for(int i = 0; i < tBvciByte.length + tFlag.length; i++){
			
			if(i < tFlag.length)
				tPayload[i] = tFlag[i];
			
			else 
				tPayload[i] = tBvciByte[i - tFlag.length];
			
		}
		AdapterFpcMessage frame2 = 
					new AdapterFpcMessage(this.pFrame.getAddress(), tPayload);
		

		this.pPipeOut.enqueue(frame2);
	}
	
	/**
	 * Restituisce l'oggetto trasformato in una stringa testuale comprensibile
	 */
	public String toString() {

		String s = "FPC (Functionality Proxy Component):\n\t";
		
		s += "Name of the device: " + this.pDeviceName + "\n\t";
		
		s += "Input pipe identifier: " + this.pPipeIn.toString() + "\n\t";

		s += "Output pipe identifier: " + this.pPipeOut.toString();
		
		return s;
	}	
	
	/**
	 * Controlla il flag del messaggio per stabire se
	 * è un normale messaggio
	 * 
	 * @param parPayload, payload da controllare
	 * @return true se è un messaggio di negativeAck
	 */
	protected boolean pIsNormalMessage(byte[] parPayload){
		
		if(MsgUtility.getFlag(parPayload) == MsgUtility.flagOthers)
			return true;
		
		return false;
	}
	
	/**
	 * Setta il tempo di sampling
	 */
	public abstract boolean setSamplingTime(long parTime, OnUnsupportedSampleRateOptions parBehaviour);
	
	/**
	 * Registra una nuova query da eseguire sul'FPC
	 * @param parAttrbiutesNames lista contenente i nomi degli attributi richiesti dalla query
	 * @return pipe sulla quale l'FPC restituira' i record, null nel caso si sia verificato un errore
	 */
	public abstract Pipe<Record> registerQuery(ArrayList<String> parAttrbiutesNames);
}